﻿using System;
using System.Globalization;

namespace NMEAInterpreter_mobile
{
    public class clsNMEAInterpreter
    {
        // Represents the EN-US culture, used for numers in NMEA sentences
        public static CultureInfo NmeaCultureInfo = new CultureInfo("en-US");
        // Used to convert knots into miles per hour
        public static double MPHPerKnot = double.Parse("1.15", NmeaCultureInfo);
        public static double KMHPerKnot = double.Parse("1.85", NmeaCultureInfo);

        #region Delegates
        public delegate void PositionReceivedEventHandler(string latitude, string longitude);
        public delegate void DateTimeChangedEventHandler(System.DateTime dateTime);
        public delegate void BearingReceivedEventHandler(string bearing);
        public delegate void FixObtainedEventHandler();
        public delegate void FixLostEventHandler();
        public delegate void SatelliteReceivedEventHandler(string pseudoRandomCode, string azimuth, string elevation, string signalToNoiseRatio);
        public delegate void HDOPReceivedEventHandler(string value);
        public delegate void VDOPReceivedEventHandler(string value);
        public delegate void PDOPReceivedEventHandler(string value);
        public delegate void SatelliteQualityEventHandler(string value);
        public delegate void SatelliteNumberEventHandler(string value);
        public delegate void SatelliteNumberInViewEventHandler(string value);
        public delegate void SatelliteModeEventHandler(string value);
        public delegate void AltitudeSeaLevelEventHandler(string value);
        public delegate void AltitudeElipsoid(string value);
        public delegate void SpeedRecievedKnotsEventHandler(string speed);
        public delegate void SpeedRecievedKmhEventHandler(string speed);
        public delegate void SpeedRecievedMphEventHandler(string speed);
        public delegate void SpeedLimitReachedEventHandler();

        #endregion

        #region Events
        public event PositionReceivedEventHandler PositionReceived;
        public event DateTimeChangedEventHandler DateTimeChanged;
        public event BearingReceivedEventHandler BearingReceived;
        public event FixObtainedEventHandler FixObtained;
        public event FixLostEventHandler FixLost;
        public event SatelliteReceivedEventHandler SatelliteReceived;
        public event HDOPReceivedEventHandler HDOPReceived;
        public event VDOPReceivedEventHandler VDOPReceived;
        public event PDOPReceivedEventHandler PDOPReceived;
        public event SatelliteQualityEventHandler SatQualityRecieved;
        public event SatelliteNumberEventHandler SatNumberRecieved;
        public event SatelliteNumberInViewEventHandler SatNumberInViewRecieved;
        public event SatelliteModeEventHandler SatModeRecieved;
        public event AltitudeSeaLevelEventHandler AltSeaLevelRecieved;
        public event AltitudeElipsoid AltElipsoidRecieved;
        public event SpeedRecievedKnotsEventHandler SpeedRecievedKnots;
        public event SpeedRecievedKmhEventHandler SpeedRecievedKmh;
        public event SpeedRecievedMphEventHandler SpeedRecievedMph;
        public event SpeedLimitReachedEventHandler SpeedLimitReached;
        #endregion

        // Processes information from the GPS receiver
        public bool Parse(string sentence)
        {
            // Discard the sentence if its checksum does not match our calculated checksum
            if (!IsValid(sentence)) return false;
            // Look at the first word to decide where to go next
            switch (GetWords(sentence)[0])
            {
                case "$GPRMC":
                    // A "Recommended Minimum" sentence was found!
                    return ParseGPRMC(sentence);
                case "$GPGSV":
                    // A "Satellites in View" sentence was recieved
                    return ParseGPGSV(sentence);
                case "$GPGSA":
                    // Interprets a "Fixed Satellites and DOP" NMEA sentence
                    return ParseGPGSA(sentence);
                case "$GPGGA":
                    // Global Positioning System Fix Data
                    return ParseGPGGA(sentence);
                case "$GPVTG":
                    // Track Made Good and Ground Speed
                    return ParseGPVTG(sentence);
                default:
                    // Indicate that the sentence was not recognized
                    return false;
            }
        }

        // Divides a sentence into individual words
        public string[] GetWords(string sentence)
        {
            sentence = sentence.Substring(0, sentence.IndexOf("*")); //remove the * and checksum from the end of the sentence
            return sentence.Split(',');
        }

        // $GPRMC - Recommended minimum specific GPS/Transit data
        public bool ParseGPRMC(string sentence)
        {
            // Divide the sentence into words
            string[] Words = GetWords(sentence);

            // Does the device currently have a satellite fix?
            if (Words[2] != "")
            {
                switch (Words[2])
                {
                    case "A":
                        if (FixObtained != null)
                            FixObtained();
                        break;
                    case "V":
                        if (FixLost != null)
                            FixLost();
                        break;
                }
            }

            // Do we have enough values to describe our location?
            if (Words[3] != "" & Words[4] != "" & Words[5] != "" & Words[6] != "")
            {
                // Yes. Extract latitude and longitude
                string Latitude = Words[3].Substring(0, 2) + "°"; // Append hours
                Latitude = Latitude + Words[3].Substring(2) + "\""; // Append minutes
                Latitude = Latitude + Words[4]; // Append the hemisphere
                string Longitude = Words[5].Substring(0, 3) + "°"; // Append hours
                Longitude = Longitude + Words[5].Substring(3) + "\""; // Append minutes
                Longitude = Longitude + Words[6]; // Append the hemisphere
                // Notify the calling application of the change
                if (PositionReceived != null)
                    PositionReceived(Latitude, Longitude);
            }

            // Do we have enough values to parse satellite-derived time and date?
            if ((Words[1] != "") && (Words[9] != ""))
            {
                // Yes. Extract hours, minutes, seconds and milliseconds
                int UtcHours = Convert.ToInt32(Words[1].Substring(0, 2));
                int UtcMinutes = Convert.ToInt32(Words[1].Substring(2, 2));
                int UtcSeconds = Convert.ToInt32(Words[1].Substring(4, 2));
                int UtcMilliseconds = 0;
                // Extract milliseconds if it is available
                if (Words[1].Length > 7)
                {
                    UtcMilliseconds = Convert.ToInt32(Words[1].Substring(7));
                }
                // Extract date
                int intDay = Convert.ToInt32(Words[9].Substring(0, 2));
                int intMonth = Convert.ToInt32(Words[9].Substring(2, 2));
                int intYear = Convert.ToInt32("20" + Words[9].Substring(4, 2));

                // Now build a DateTime object with all values
                System.DateTime Today = System.DateTime.Now.ToUniversalTime();
                //System.DateTime SatelliteTime = new System.DateTime(Today.Year, Today.Month, Today.Day, UtcHours, UtcMinutes, UtcSeconds, UtcMilliseconds);
                System.DateTime SatelliteTime = new System.DateTime(intYear, intMonth, intDay, UtcHours, UtcMinutes, UtcSeconds, UtcMilliseconds);
                // Notify of the new time, adjusted to the local time zone
                if (DateTimeChanged != null)
                    DateTimeChanged(SatelliteTime.ToLocalTime());
            }

            // Do we have enough information to extract the current speed?
            if (Words[7] != "")
            {
                // Yes.  Parse the speed and convert it to MPH
                double SpeedKnots = double.Parse(Words[7], NmeaCultureInfo);
                double SpeedMph = double.Parse(Words[7], NmeaCultureInfo) * MPHPerKnot;
                double SpeedKmh = double.Parse(Words[7], NmeaCultureInfo) * KMHPerKnot;

                // Notify of the new speed
                if (SpeedRecievedKnots != null)
                {
                    SpeedRecievedKnots(SpeedKnots.ToString());
                    SpeedRecievedMph(SpeedMph.ToString());
                    SpeedRecievedKmh(SpeedKmh.ToString());
                }
                // Are we over the highway speed limit?
                if (SpeedKmh > 70)
                    if (SpeedLimitReached != null)
                        SpeedLimitReached();
            }

            // Do we have enough information to extract bearing?
            if (Words[8] != "")
            {
                // Indicate that the sentence was recognized
                double Bearing = double.Parse(Words[8], NmeaCultureInfo);
                if (BearingReceived != null)
                    BearingReceived(Bearing.ToString());
            }

            // Indicate that the sentence was recognized
            return true;
        }

        // $GPGSV - GPS Satellites in view
        public bool ParseGPGSV(string sentence)
        {
            int PseudoRandomCode = 0;
            int Azimuth = 0;
            int Elevation = 0;
            int SignalToNoiseRatio = 0;
            string strPseudoRandomCode = "0";
            string strAzimuth = "0";
            string strElevation = "0";
            string strSignalToNoiseRatio = "0";

            // Divide the sentence into words
            string[] Words = GetWords(sentence);

            //Total number of SVs in view
            if (Words[3] != "")
            {
                if (SatNumberInViewRecieved != null)
                {
                    SatNumberInViewRecieved(Words[3]);
                }
            }

            // Each sentence contains four blocks of satellite information.  Read each block
            // and report each satellite's information
            int Count = 0;
            for (Count = 1; Count <= 4; Count++)
            {
                // Does the sentence have enough words to analyze?
                if ((Words.Length - 1) >= (Count * 4 + 3))
                {
                    // Yes.  Proceed with analyzing the block.  Does it contain any information?
                    if (Words[Count * 4] != "" & Words[Count * 4 + 1] != "" & Words[Count * 4 + 2] != "" & Words[Count * 4 + 3] != "")
                    {
                        // Yes. Extract satellite information and report it
                        PseudoRandomCode = System.Convert.ToInt32(Words[Count * 4]);
                        Elevation = Convert.ToInt32(Words[Count * 4 + 1]);
                        Azimuth = Convert.ToInt32(Words[Count * 4 + 2]);
                        //SignalToNoiseRatio = Convert.ToInt32(Words[Count * 4 + 2]);
                        SignalToNoiseRatio = Convert.ToInt32(Words[Count * 4 + 3]);
                        strPseudoRandomCode = PseudoRandomCode.ToString();
                        strAzimuth = Azimuth.ToString();
                        strElevation = Elevation.ToString();
                        strSignalToNoiseRatio = SignalToNoiseRatio.ToString();
                        // Notify of this satellite's information
                        if (SatelliteReceived != null)
                            SatelliteReceived(strPseudoRandomCode, strAzimuth, strElevation, strSignalToNoiseRatio);
                    }
                }
            }

            // Indicate that the sentence was recognized
            return true;
        }

        // $GPGSA - GPS DOP and active satellites
        public bool ParseGPGSA(string sentence)
        {
            // Divide the sentence into words
            string[] Words = GetWords(sentence);
            double PDOP, HDOP, VDOP;

            // Satellite mode
            if (Words[2] != "") //Mode: 1=Fix not available, 2=2D, 3=3D
            {
                if (SatModeRecieved != null)
                {
                    SatModeRecieved(Words[2]);
                }
            }

            // DOP values
            if (Words[15] != "") //PDOP
            {
                if (PDOPReceived != null)
                {
                    PDOP = double.Parse(Words[15], NmeaCultureInfo);
                    PDOPReceived(PDOP.ToString());
                }
            }
            if (Words[16] != "") //HDOP
            {
                if (HDOPReceived != null)
                {
                    HDOP = double.Parse(Words[16], NmeaCultureInfo);
                    HDOPReceived(HDOP.ToString());
                }
            }
            if (Words[17] != "") //VDOP
            {
                if (VDOPReceived != null)
                {
                    VDOP = double.Parse(Words[17], NmeaCultureInfo);
                    VDOPReceived(VDOP.ToString());
                }
            }
            return true;
        }

        // $GPGGA - Global Positioning System Fix Data
        public bool ParseGPGGA(string sentence)
        {
            string[] Words = GetWords(sentence);

            double AltSeaLevel, AltElipsoid;

            if (Words[6] != "") //GPS quality indicator (0=invalid; 1=GPS fix; 2=Diff. GPS fix)
            {
                if (SatQualityRecieved != null)
                {
                    SatQualityRecieved(Words[6]);
                }
            }
            if (Words[7] != "") //Number of satellites in use [not those in view]
            {
                if (SatNumberRecieved != null)
                {
                    SatNumberRecieved(Words[7]);
                }
            }
            if (Words[9] != "") //Antenna altitude above/below mean sea level (geoid)
            {
                if (AltSeaLevelRecieved != null)
                {
                    AltSeaLevel = double.Parse(Words[9], NmeaCultureInfo);
                    AltSeaLevelRecieved(AltSeaLevel.ToString());
                }
            }
            if (Words[10] != "") //Meters  (Antenna height unit)
            {
                //
            }
            if (Words[11] != "") //Geoidal separation (Diff. between WGS-84 earth ellipsoid and mean sea level . -=geoid is below WGS-84 ellipsoid)
            {
                if (AltElipsoidRecieved != null)
                {
                    AltElipsoid = double.Parse(Words[11], NmeaCultureInfo);
                    AltElipsoidRecieved(AltElipsoid.ToString());
                }
            }

            return true;
        }

        // $GPBOD - Bearing, origin to destination
        public bool ParseGPBOD(string sentence)
        {
            return true;
        }

        // $GPBWC - Bearing and distance to waypoint, great circle
        public bool ParseGPBWC(string sentence)
        {
            return true;
        }

        // $GPGLL - Geographic position, latitude / longitude
        public bool ParseGPGLL(string sentence)
        {
            return true;
        }

        //$GPR00 - List of waypoints in currently active route
        public bool ParseGPR00(string sentence)
        {
            return true;
        }

        //$GPRMA - Recommended minimum specific Loran-C data
        public bool ParseGPRMA(string sentence)
        {
            return true;
        }

        //$GPRMB - Recommended minimum navigation info
        public bool ParseGPRMB(string sentence)
        {
            return true;
        }

        //$GPRTE - Routes
        public bool ParseGPRTE(string sentence)
        {
            return true;
        }

        //$GPTRF - Transit Fix Data
        public bool ParseGPTRF(string sentence)
        {
            return true;
        }

        //$GPSTN - Multiple Data ID
        public bool ParseGPSTN(string sentence)
        {
            return true;
        }

        //$GPVBW - Dual Ground / Water Speed
        public bool ParseGPVBW(string sentence)
        {
            return true;
        }

        //$GPVTG - Track made good and ground speed
        public bool ParseGPVTG(string sentence)
        {
            // Divide the sentence into words
            string[] Words = GetWords(sentence);
            double SpeedKnots, SpeedKmh;

            // Do we have speed in knots
            if (Words[5] != "")
            {
                if (SpeedRecievedKnots != null)
                {
                    SpeedKnots = double.Parse(Words[5], NmeaCultureInfo);
                    SpeedRecievedKnots(SpeedKnots.ToString());
                }
            }

            //Do we have speed in km/h
            if (Words[7] != "")
            {
                if (SpeedRecievedKmh != null)
                {
                    SpeedKmh = double.Parse(Words[7], NmeaCultureInfo);
                    SpeedRecievedKmh(SpeedKmh.ToString());
                }
            }

            return true;
        }

        //$GPWPL - Waypoint location
        public bool ParseGPWPL(string sentence)
        {
            return true;
        }

        //$GPXTE - Cross-track error, Measured
        public bool ParseGPXTE(string sentence)
        {
            return true;
        }

        //$GPZDA - Date & Time
        public bool ParseGPZDA(string sentence)
        {
            return true;
        }

        // Returns True if a sentence's checksum matches the calculated checksum
        public bool IsValid(string sentence)
        {
            // Compare the characters after the asterisk to the calculation
            return sentence.Substring(sentence.IndexOf("*") + 1) == GetChecksum(sentence);
        }

        // Calculates the checksum for a sentence
        public string GetChecksum(string sentence)
        {
            // Loop through all chars to get a checksum
            //INSTANT C# NOTE: Commented this declaration since looping variables in 'foreach' loops are declared in the 'foreach' header in C#
            //        char Character = '\0';
            int Checksum = 0;
            foreach (char Character in sentence)
            {
                if (Character == '$')
                {
                    // Ignore the dollar sign
                }
                else if (Character == '*')
                {
                    // Stop processing before the asterisk
                    break;
                }
                else
                {
                    // Is this the first value for the checksum?
                    if (Checksum == 0)
                    {
                        // Yes. Set the checksum to the value
                        Checksum = Convert.ToByte(Character);
                    }
                    else
                    {
                        // No. XOR the checksum with this character's value
                        Checksum = Checksum ^ Convert.ToByte(Character);
                    }
                }
            }
            // Return the checksum formatted as a two-character hexadecimal
            return Checksum.ToString("X2");
        }
    }
}
